import logging

import cv2
import numpy as np
from django.db import models
from model_utils.models import SoftDeletableModel

from apps.scene.models import Scene

logger = logging.getLogger('log')


class CameraModel(SoftDeletableModel):
    """
    相机型号
    """
    camera_model_name = models.CharField(verbose_name='相机型号名称', max_length=255, null=True, unique=True)
    camera_inner_dx = models.FloatField(verbose_name='相机内参f/dx', null=True, blank=True)
    camera_inner_dy = models.FloatField(verbose_name='相机内参f/dy', null=True, blank=True)
    camera_inner_u0 = models.FloatField(verbose_name='相机内参u0', null=True, blank=True)
    camera_inner_v0 = models.FloatField(verbose_name='相机内参v0', null=True, blank=True)
    camera_inner_dist = models.JSONField(verbose_name='畸变系数', null=True, blank=True)
    camera_type_choice = (
        (1, '单目相机'),
        (0, '鱼眼相机'),
    )
    camera_type = models.SmallIntegerField(verbose_name='相机类型', choices=camera_type_choice, default=1)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name='更新时间', auto_now=True)

    class Meta:
        db_table = "cv_camera_model"

    def __str__(self):
        return f'相机型号id:{self.id},相机类型:{self.camera_type}'

    # 设置内参
    def set_mtx(self, mtx):
        try:
            self.camera_inner_dx = mtx[0][0]
            self.camera_inner_dy = mtx[1][1]
            self.camera_inner_u0 = mtx[0][2]
            self.camera_inner_v0 = mtx[1][2]
            return True
        except Exception as error:
            logger.error(error)
            return False

    # 内参转换
    def get_mtx(self):
        if self.check_camera_inner():
            mtx = np.zeros((3, 3))
            mtx[0][0] = self.camera_inner_dx
            mtx[1][1] = self.camera_inner_dy
            mtx[0][2] = self.camera_inner_u0
            mtx[1][2] = self.camera_inner_v0
            mtx[2][2] = 1
            return mtx
        else:
            return None

    # 设置畸变系数
    def set_dist(self, dist):
        try:
            camera_dist = {}
            for i in range(dist.shape[1]):
                camera_dist.setdefault(f'dist{i + 1}', dist[0][i])
            self.camera_inner_dist = camera_dist
            return True
        except Exception as error:
            logger.error(error)
            return False

    # 畸变系数转换
    def get_dist(self):
        if self.check_camera_inner_dist():
            try:
                camera_dist = self.camera_inner_dist
                # 检验相机的畸变系数
                dist_len = len(camera_dist)
                dist = np.zeros((1, dist_len))
                for i in range(dist_len):
                    dist[0][i] = camera_dist.get(f'dist{i + 1}')
                return dist
            except Exception as error:
                logger.error(error)
                return None
        else:
            return None

    # 检验相机型号畸变系数
    def check_camera_inner_dist(self, dist: dict = None):
        try:
            if dist is None:
                dist = self.camera_inner_dist
            # 检验相机的畸变系数
            dist_len = len(dist)
            # ToDO:限制畸变系数长度
            if dist_len < 3:
                # dist畸变系数中至少存在5个参数
                return False
            else:
                # 畸变系数的顺序对内参有影响，所以畸变系数顺序应该保存规格，如dist1，dist2。。。不能缺少，否则影响系数
                # 所以畸变系数如不符合dist1 ---》 dist(dist_len)的话，即为验证失败
                for i in range(dist_len):
                    try:
                        # 按照规格获取，判断值是否为数值型数据
                        float(dist.get(f'dist{i + 1}'))
                    except (ValueError, TypeError):
                        # 遇到异常，说明不是数值型数据
                        return False
                else:
                    # 遍历完无异常，正常
                    return True
        except Exception as error:
            logger.error(error)
            return False

    # 检验相机内参数
    def check_camera_inner(self):
        try:
            # 检查相机的内参数
            if (self.camera_inner_dx is None
                    or self.camera_inner_dy is None
                    or self.camera_inner_u0 is None
                    or self.camera_inner_v0 is None
                    or self.camera_inner_dist is None):
                # 只要有一个相机的内参为None，内参检验失败
                self.inner_check = False
                return False
            else:
                if self.check_camera_inner_dist():
                    # 判断畸变系数是否规格
                    self.inner_check = True
                    return True
                else:
                    self.inner_check = False
                    return False
        except Exception as error:
            logger.error(error)
            self.inner_check = False
            return False

    # 类型转换
    def camera_type_trans(self):
        # 相机类型转换，将相机type转换为相机的具体类型
        if self.camera_type == 0:
            self.camera_type = '鱼眼相机'
        elif self.camera_type == 1:
            self.camera_type = '单目相机'


class Camera(SoftDeletableModel):
    """
    相机
    """
    camera_name = models.CharField(verbose_name='相机名称', max_length=255)
    camera_ip = models.GenericIPAddressField(verbose_name='相机IP', max_length=15, null=True, blank=True)
    camera_port = models.CharField(verbose_name='相机端口号', max_length=5, default='554', null=True, blank=True)
    camera_username = models.CharField(verbose_name='相机用户名', max_length=255, null=True, blank=True)
    camera_password = models.CharField(verbose_name='相机密码', max_length=255, null=True, blank=True)
    # 一对多关系，在多表中创建外键
    # 一个相机型号管理多个相机 ——> 多：相机
    camera_model = models.ForeignKey(to=CameraModel, verbose_name='相机型号', on_delete=models.SET_NULL, null=True,
                                     blank=True)
    camera_rvecs = models.JSONField(verbose_name='旋转向量', null=True, blank=True)
    camera_tvecs = models.JSONField(verbose_name='平移向量', null=True, blank=True)
    # 一个场景存在多个相机 ——> 多：相机
    camera_scene = models.ForeignKey(to=Scene, verbose_name='相机场景', on_delete=models.SET_NULL, null=True, blank=True)
    camera_scene_info = models.JSONField(verbose_name='场景信息', null=True, blank=True)
    camera_api = models.TextField(verbose_name='视频服务器接口', null=True, blank=True)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name='更新时间', auto_now=True)

    class Meta:
        db_table = "cv_camera"

    # 设置畸变系数
    def set_rvecs(self, rvecs):
        try:
            print(rvecs)
            camera_rvecs = {}
            for i in range(rvecs.shape[1]):
                camera_rvecs.setdefault(f'rvecs{i + 1}', rvecs[0][i])
            self.camera_rvecs = camera_rvecs
            return True
        except Exception as error:
            logger.error(error)
            return False

    # 旋转向量转换
    def get_rvecs(self):
        if self.check_camera_outer_rvecs():
            try:
                camera_rvecs = self.camera_rvecs
                # 检验相机的畸变系数
                rvecs_len = len(camera_rvecs)
                rvecs = np.zeros((1, rvecs_len))
                for i in range(rvecs_len):
                    rvecs[0][i] = camera_rvecs.get(f'rvecs{i + 1}')
                return rvecs
            except Exception as error:
                logger.error(error)
                return None
        else:
            return None

    # 旋转矩阵获取
    def get_rmat(self):
        rvecs = self.get_rvecs()
        if rvecs is None:
            return None
        else:
            rmat = cv2.Rodrigues(rvecs)[0]
            return rmat

    # 设置畸变系数
    def set_tvecs(self, tvecs):
        try:
            camera_tvecs = {}
            for i in range(tvecs.shape[0]):
                camera_tvecs.setdefault(f'tvecs{i + 1}', tvecs[i][0])
            self.camera_tvecs = camera_tvecs
            return True
        except Exception as error:
            logger.error(error)
            return False

    # 平移向量转换
    def get_tvecs(self):
        if self.check_camera_outer_rvecs():
            try:
                camera_tvecs = self.camera_tvecs
                # 检验相机的畸变系数
                tvecs_len = len(camera_tvecs)
                tvecs = np.zeros((tvecs_len, 1))
                for i in range(tvecs_len):
                    tvecs[i][0] = camera_tvecs.get(f'tvecs{i + 1}')
                return tvecs
            except Exception as error:
                logger.error(error)
                return None
        else:
            return None

    # 检验相机旋转向量
    def check_camera_outer_rvecs(self, rvecs: dict = None):
        """
        检验相机的旋转向量
        :return: 若为浮点数值型数据返回True,否则返回False
        """
        try:
            if rvecs is None:
                rvecs = self.camera_rvecs
            rvecs_len = len(rvecs)
            # ToDo:旋转向量长度限制
            if rvecs_len < 3:
                return False
            else:
                for i in range(rvecs_len):
                    try:
                        # 遍历判断每个值是否为浮点数值型数据
                        float(rvecs.get(f'rvecs{i + 1}'))
                    except (ValueError, TypeError):
                        return False
                else:
                    # 无异常，返回True
                    return True
        except Exception as error:
            logger.error(error)
            return False

    # 检验相机平移向量
    def check_camera_outer_tvecs(self, tvecs: dict = None):
        """
        检验相机的平移向量
        :return: 若为浮点数值型数据返回True,否则返回False
        """
        try:
            if tvecs is None:
                tvecs = self.camera_tvecs
            # ToDo:平移向量长度限制
            tvecs_len = len(tvecs)
            if tvecs_len < 3:
                return False
            else:
                for i in range(tvecs_len):
                    try:
                        # 遍历判断每个值是否为浮点数值型数据
                        float(tvecs.get(f'tvecs{i + 1}'))
                    except (ValueError, TypeError):
                        return False
                else:
                    # 无异常，返回True
                    return True
        except Exception as error:
            logger.error(error)
            return False

    # 检验相机外参
    def check_camera_outer(self):
        """
        检验相机外参
        :return: 若外参(旋转向量和平移向量)都通过检验返回True，否则返回False
        """
        try:
            # 若旋转向量或平移向量某一为None，外参检验失败
            if (self.camera_rvecs is None
                    or self.camera_tvecs is None):
                self.outer_check = False
                return False
            else:
                # 判断外参(旋转向量与平移向量)是否皆符合规范
                if self.check_camera_outer_rvecs() and self.check_camera_outer_tvecs():
                    self.outer_check = True
                    return True
                else:
                    self.outer_check = False
                    return False
        except Exception as error:
            logger.error(error)
            self.outer_check = False
            return False

    # 检验视频访问接口
    def check_camera_api(self):
        try:
            # 相机IP、端口、名称、密码同时存在或存在相机Api时返回True
            if ((self.camera_ip is None or self.camera_ip == ''
                 or self.camera_port is None or self.camera_port == ''
                 or self.camera_username is None or self.camera_username == ''
                 or self.camera_password is None or self.camera_password == '')
                    or self.camera_api is None):
                self.api_check = False
                return False
            else:
                self.api_check = True
                return True
        except Exception as error:
            logger.error(error)
            self.api_check = False
            return False

    # 检验相机
    def check_camera(self):
        """
        检验相机外参
        :return: 若外参(旋转向量和平移向量)都通过检验返回True，否则返回False
        """
        try:
            if self.camera_model is None:
                self.camera_check = False
                return False
            else:
                if self.camera_model.check_camera_inner() and self.check_camera_outer() and self.check_camera_api():
                    self.camera_check = True
                    return True
                else:
                    self.camera_check = False
                    return False
        except Exception as error:
            logger.error(error)
            self.camera_check = False
            return False
